<?php
/* this file stores all of the functions that are used
over all of the other files. */

ini_set("log_errors", 1);
$srv = $user_settings['instance'];
//$token = ($token != false ? msc($token,'d') : false);
$token = ($token != false ? $token : false);






// FUNCTIONS THAT HAVE TO DO WITH INSTANCE COMMUNICATION AND RETRIEVAL

/* a function to make an authenticated general GET api call to the logged-in instance.
   - $url is a string of an api call like "account/:id/statuses" 
   
   - returns the array conversion of the json response
 */
function api_get($url) {
	global $srv;
	global $token;

	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, "https://$srv/api/v1/" . $url);
	if (!is_null($token)) {
		curl_setopt($curl, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
	}
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	$result = curl_exec($curl);
	curl_close($curl);
	return json_decode($result, true);
}

/* same as above but used in some newer api endpoinds (v2) */
function api_getv2($url) {
	global $srv;
	global $token;

	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, "https://$srv/api/v2/" . $url);
	if (!is_null($token)) {
		curl_setopt($curl, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
	}
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	$result = curl_exec($curl);
	curl_close($curl);

	return json_decode($result, true);
}

/* a function to make an authenticated general POST api call to the logged-in instance.
   - $url is a string of an api call like "account/:id/statuses" 
   
   - returns the array conversion of the json response
 */
function api_post($url, $array) {
	global $srv;
	global $token;

	$cSession = curl_init();
	curl_setopt($cSession, CURLOPT_HEADER, false);
	curl_setopt($cSession, CURLOPT_POST, 1);
	curl_setopt($cSession, CURLOPT_URL, "https://$srv/api/v1/" . $url);
	if (!is_null($token)) {
		curl_setopt($cSession, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
	}
	curl_setopt($cSession, CURLOPT_POSTFIELDS, http_build_query($array));
	curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
	$result = curl_exec($cSession);
	curl_close($cSession);

	return json_decode($result, true);
}

/* a function to make an authenticated general DELETE api call to the logged-in instance.
   - $url is a string of an api call like "statuses/delete/:id" 
   
   - returns the array conversion of the json response
 */
function api_delete($url, $array) {
	global $srv;
	global $token;

	$cSession = curl_init();
	curl_setopt($cSession, CURLOPT_HEADER, false);
	curl_setopt($cSession, CURLOPT_POST, 1);
	curl_setopt($cSession, CURLOPT_URL, "https://$srv/api/v1/" . $url);
	curl_setopt($cSession, CURLOPT_CUSTOMREQUEST, "DELETE");
	if (!is_null($token)) {
		curl_setopt($cSession, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
	}
	curl_setopt($cSession, CURLOPT_POSTFIELDS, http_build_query($array));
	curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
	$result = curl_exec($cSession);
	curl_close($cSession);

	return json_decode($result, true);
}

/* a function to make an authenticated general PATCH api call to the logged-in instance.
   - $url is a string of an api call like "account/:id/statuses" 
   
   - returns the array conversion of the json response
 */
function api_patch($url, $array) {
	global $srv;
	global $token;

	$cSession = curl_init();
	curl_setopt($cSession, CURLOPT_HEADER, false);
	curl_setopt($cSession, CURLOPT_POST, 1);
	curl_setopt($cSession, CURLOPT_URL, "https://$srv/api/v1/" . $url);
	curl_setopt($cSession, CURLOPT_CUSTOMREQUEST, "PATCH");
	if (!is_null($token)) {
		curl_setopt($cSession, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
	}
	curl_setopt($cSession, CURLOPT_POSTFIELDS, http_build_query($array));
	curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
	$result = curl_exec($cSession);
	curl_close($cSession);

	return json_decode($result, true);
}

/* Function to upload files to the profile of the authenticated user
   - $type can be 'avatar' or 'header'
   
   - returns the json of the api call
   */
function upload_profile($file,$type){
	global $srv;
	global $token;
	
	$mime = get_mime($file);
	$info = pathinfo($file);
	$name = $info['basename'];
	$output = new CURLFile($file, $mime, $name);
		$cSession = curl_init();
		curl_setopt($cSession, CURLOPT_URL, "https://$srv/api/v1/accounts/update_credentials");
		curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($cSession, CURLOPT_POST, 1);
		curl_setopt($cSession, CURLOPT_CUSTOMREQUEST, "PATCH");
		curl_setopt($cSession, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
		curl_setopt($cSession, CURLOPT_POSTFIELDS, array(
			$type => $output
		));
		$result = curl_exec($cSession);
		curl_close($cSession);
		return $result;
}

/* this function fetches all the data from a profile (bio, username, relationships, etc) 
	- $user is the id of the queried user
	
	- returns the array conversion of the json response
*/
function user_info($user) {
	global $user_settings;
	$info = api_get("accounts/" . $user);
	$rel = api_get("accounts/relationships?id=" . $user);
	return array(
		$info,
		$rel
	);
}

/* this function fetches the context (the previous posts and replies) of a specified post 
   - $post = ID of the post queried.
   
   - returns an array conversion of the json response
*/
function context($post) {
	global $srv;
	global $token;

	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, "https://$srv/api/v1/statuses/$post/context");
	if (!is_null($token)) {
		curl_setopt($curl, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
	}
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
	$result = curl_exec($curl);
	curl_close($curl);
	return $result;
}


/* a function to fav or unfav a specified post 
	- $post = ID of the post queried.
	- $mode can be true if is a fav or false if it's an unfav
	
	- returns the number of favs of the post after the specified action or "error"
	  if there was an error performing the action
*/
function favourite($post, $mode) {
	$result = api_post(($mode == true ? "statuses/$post/favourite" : "statuses/$post/unfavourite"),array());

	if (isset($result['favourites_count'])) {
		return $result['favourites_count'];
	}
	else {
		return "error";
	}
}


/* a function to reblog or unreblog a specified post 
	- $post = ID of the post queried.
	- $mode can be true if is a reblog or false if it's an unreblog
	
	- returns the number of reblogs of the post after the specified action or "error"
	  if there was an error performing the action
*/
function reblog($post, $mode) {
	$result = api_post(($mode == true ? "statuses/$post/reblog" : "statuses/$post/unreblog"),array());
	if (isset($result['reblog']['reblogs_count'])) {
		return $result['reblog']['reblogs_count'];
	}
	elseif (isset($result['reblogs_count'])) {
		return $result['reblogs_count'];
	}
	else {
		return "error";
	}
}



/* function to delete a post 
   - $id is the id of the post to delete
   
   - returns 1 if the post was deleted succesfully or 0 if there was an error
*/
function delpost($id) {
	global $srv;
	global $token;

	if (!is_null($token)) {
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_HEADER, false);
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_URL, "https://$srv/api/v1/statuses/$id");
		curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
		curl_setopt($curl, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));

		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		$result = curl_exec($curl);
		curl_close($curl);
		$result = json_decode($result, true);
		if (empty($result)) {
			return "1";
		}
		else {
			return "0";
		}
	}
}

/* function to issue a vote to a poll
	- $id is the id of the poll to vote on
	
	- $choices is a comma separated string of the choices that will be voted.
*/
function vote($poll, $choices) {
	global $srv;
	global $token;
	if (!is_null($token)) {
		$cSession = curl_init();
		curl_setopt($cSession, CURLOPT_URL, "https://$srv/api/v1/polls/$poll/votes");
		curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($cSession, CURLOPT_POST, 1);
		curl_setopt($cSession, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
		$query = "";
		$choicelist = explode(",",$choices);
		foreach($choicelist as $choice){
			$query .= "choices[]=$choice&";
		}
		
		curl_setopt($cSession, CURLOPT_POSTFIELDS, $query);
		$result = curl_exec($cSession);
		curl_close($cSession);
		return $result;
	}
	else {
		return false;
	}
}


/* function to send a new post to the logged in instance
	- $text = the body of the message
	- $media = array of uploaded media id's
	- $reply = the id of a post being replied to, can be null.
	- $markdown = specify if the post uses markdown (unused at this moment)
	- $scope = the scope of the post (public, private, unlisted or direct)
	- $sensitive = bool to specify if the post media is sensitive
	- $spoiler = string of the title of the post
	
	- returns the json of the api response
   */
function sendpost($text, $media, $reply = null, $markdown = false, $scope = "public", $sensitive = false, $spoiler = false) {
	global $srv;
	global $token;
	if (!is_null($token)) {

		$cSession = curl_init();
		curl_setopt($cSession, CURLOPT_URL, "https://$srv/api/v1/statuses");
		curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($cSession, CURLOPT_POST, 1);
		curl_setopt($cSession, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $token
		));
		$query = "";
		$query .= "status=" . urlencode(html_entity_decode($text, ENT_QUOTES)) . "&visibility=" . $scope;;
		if (!is_null($reply) && $reply != "null") {
			$query .= "&in_reply_to_id=" . $reply;
		}
		if ($markdown == true) {
			$query .= "&content_type=text/markdown";
		}
		if ($sensitive == 'true') {
			$query .= "&sensitive=true";
		}
		if ($spoiler == true) {
			$query .= "&spoiler_text=" . $spoiler;
		}

		if (!is_null($media)) {
			foreach ($media as $mid) {
				$query .= "&media_ids[]=" . $mid;
			}
		}
		
		curl_setopt($cSession, CURLOPT_POSTFIELDS, $query);
		$result = curl_exec($cSession);
		curl_close($cSession);
		return $result;
	}
	else {
		return false;
	}
}

/* uploads a file to the logged in instance.
	- $file = path of the file to upload on local storage
	
	- returns an array where: 
	    0: the ID of the uploaded file
		1: the url of the file (if the file isn't an image, returns a placeholder)
   */
function uploadpic($file) {
	global $srv;
	global $token;

	if (!is_null($token)) {
		$mime = get_mime($file);
		$info = pathinfo($file);
		$name = $info['basename'];
		$output = new CURLFile($file, $mime, $name);
			$cSession = curl_init();
			curl_setopt($cSession, CURLOPT_URL, "https://$srv/api/v1/media");
			curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
			curl_setopt($cSession, CURLOPT_POST, 1);
			curl_setopt($cSession, CURLOPT_HTTPHEADER, array(
				'Authorization: Bearer ' . $token
			));
			curl_setopt($cSession, CURLOPT_POSTFIELDS, array(
				'file' => $output
			));
			$result = curl_exec($cSession);
		curl_close($cSession);
		echo $result;
		$array = json_decode($result, true);

		$ext = explode(".", $array['url']);
		$ext = end($ext);
		$ext = explode("?", $ext) [0];

		if (in_array($ext, array('jpg','jpeg','gif','png','svg','webm'))) {
			$file = $array['url'];
		} elseif (in_array($ext, array('mp4','webp','ogv'))) {
			$file = "img/vid.png";
		} elseif (in_array($ext, array('mp3','ogg','oga','opus'))) {
			$file = "img/aud.png";
		} else {
			$file = "img/doc.png";
		}
		return json_encode(array(
			$array['id'],
			$file
		));
	}
	else {
		return false;
	}
}

/* this is used to register DashFE as an application on an instance 
   mostly used when logging in
   - $instance = the url of the instance where to log in
   
   - returns the array conversion of the json response
 */
function register_app($instance) {
	global $setting;

	$cSession = curl_init();
	curl_setopt($cSession, CURLOPT_URL, "https://$instance/api/v1/apps");
	curl_setopt($cSession, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($cSession, CURLOPT_HEADER, false);
	curl_setopt($cSession, CURLOPT_POST, 1);
	curl_setopt($cSession, CURLOPT_POSTFIELDS, http_build_query(array(
		'client_name' => $setting['appname'],
		'redirect_uris' => $setting['url'],
		'scopes' => 'read write follow'
	)));
	$result = curl_exec($cSession);
	curl_close($cSession);

	return json_decode($result, true);
}

/* this function will get all the notes (reblogs and favs) that has an specified post
	- $thread = the id of the post queried
	
	- returns an array with all the notes
	  each note is another array where:
	  0 = type of note ("reb" reblog or "fab" favorite)
	  1 = array of the details of the user, converted from the json of the api response.
 */ 
function getnotes($thread) {
	global $user_settings;
	global $token;
	global $srv;
	global $setting;
	global $logedin;
	@$reb = array(
		api_get("statuses/" . $thread . "/reblogged_by")
	);
	@$fab = array(
		api_get("statuses/" . $thread . "/favourited_by")
	);
	$limit = (count($reb[0]) > count($fab[0]) ? count($reb[0]) - 1 : count($fab[0]) - 1);

	$notes = array();
	$index = 0;

	for ($i = 0;$i <= $limit;$i++) {
		if (isset($reb[0][$i])) {
			$notes[$index][0] = "reb";
			$notes[$index][1] = $reb[0][$i];
			$index++;
		}
		if (isset($fab[0][$i])) {
			$notes[$index][0] = "fav";
			$notes[$index][1] = $fab[0][$i];
			$index++;
		}
	}

	return $notes;
}

/* this function will fetch replies of a post
	- $thread = the id of the post from where to fetch replies
	- $since = id of a post. If specified, the function will fetch the replies 
	           only since the specified id.
			   
	- returns an array with all the replies
	  each element is another array where:
	  'mode' = type of the reply (ancestor or descendant)
	  'content' = array of the contents of the reply, converted from the json of the api response.
 */ 
function getreplies($thread, $since = false) {
	global $user_settings;
	global $token;
	global $srv;
	global $setting;
	global $logedin;
	$context = json_decode(context($thread) , true);
	$array = array();
	if (!empty($context['ancestors'])) {
		if ($since == false) {
			foreach ($context['ancestors'] as $elem) {
				$elem['type'] = 'ancestor';
				$array[] = $elem;
			}
		}
	}
	$flag = 0;
	if (!empty($context['descendants'])) {
		foreach ($context['descendants'] as $elem) {
			if (($since != false && $flag == 1) || $since == false) {
				$elem['type'] = 'descendant';
				$array[] = $elem;
			}
			if ($since != false && $elem['id'] == $since) {
				$flag = 1;
			}
		}
	}

	$replies = array();

	foreach ($array as $item) {
		$reply['mode'] = "";
		if ($item['type'] == 'ancestor') {
			$reply['mode'] = "ancestor";
		}

		$replies[] = array(
			'mode' => $reply['mode'],
			'content' => $item
		);
	}

	return $replies;
}


/* this function takes some options from the init.php file and the user_settings cookie 
   and fetches all the posts of the appropiate timeline 
   - $query = an array with a set of elements generated by the file "include/init.php" 
   
   - returns an array of the posts list fetched converted from the json response of the api.  
   */
function timeline($query) {
	global $token;
	global $srv;

	$notes = "";
	
	$hq = array();
	$hq['limit'] = 10;
	$hq['only_media'] = ($query['text'] == "off" ? 'true' : 'false');
	if ($query['next']){
		$hq['max_id'] = $query['next'];
		$next = $query['next'];
	} elseif ($query['since']) {
		$hq['since_id'] = $query['since'];
	}
	
	switch ($query['mode']) {
		case "home":
			$array = api_get("timelines/home?".http_build_query($hq));
		break;

		case "federated":
			$array = api_get("timelines/public?".http_build_query($hq));
		break;

		case "tag":
			$array = api_get("timelines/tag/" . $query['tag'] . "?".http_build_query($hq));
		break;

		case "local":
			$array = api_get("timelines/public?local=true&".http_build_query($hq));
		break;

		case "user":
			$array = api_get("accounts/" . $query['user'] . "/statuses?".http_build_query($hq));
		break;

		case "thread":
			$array = array(
				api_get("statuses/" . $query['thread'])
			);
		break;

		case "favourites":
			$array = api_get("favourites?".http_build_query($hq));
		break;

		case "direct":
			$array = api_get("timelines/direct?".http_build_query($hq));
		break;

		case "list":
			$array = api_get("timelines/list/" . $query['list'] . "?".http_build_query($hq));
		break;

		case "bookmarks":
			$array = api_get("bookmarks?".http_build_query($hq));
		break;
		
		case "search":
			$array = api_getv2("search?limit=40&q=".$query['search']."{$next}")['statuses'];
		break;

		case "account":
			$info = api_get("accounts/verify_credentials");
			$array = api_get("accounts/" . $info['id'] . "/statuses?".http_build_query($hq));
		break;

		default:
			$array = api_get("timelines/public?".http_build_query($hq));
		break;
	}

	if (!is_array($array)) {
		return false;
	}

	$next = end($array) ['id'];
	$thread = array();
	/*
	foreach ($array as $elem) {
		if ($query['replies'] == "on" || $query['mode'] == "thread") {
			$thread[] = $elem;
		}
		else {
			if ($elem['in_reply_to_id'] == null) {
				$thread[] = $elem;
			}
		}

	}*/
 
	foreach ($array as $elem) {
		if ($query['replies'] == "on" || $query['mode'] == "thread") {
			$thread[] = $elem;
		}
		else {
			if ($elem['in_reply_to_id'] == null || $elem['in_reply_to_account_id'] == $query['uid']) {
				$thread[] = $elem;
			} else {
				$rel = api_get("accounts/relationships?id=" . $elem['in_reply_to_account_id']);
				if ($rel[0]['following']){
					$thread[] = $elem;
				}
			}
		}

	}

	return $thread;
}










// FUNCTIONS THAT HAVE TO DO WITH RENDERING STUFF FOR THE PAGE

/* this function is used to generate the html code of a poll */
function renderPoll($elem) {
	global $logedin;
	
	$output = "";
	$output .= "<br>";
	$votes = $elem['poll']['votes_count'];
	
	if ($elem['poll']['voted'] || $elem['poll']['expired']) {
		$output.= "<b>Votes: $votes</b><br>";
		foreach ($elem['poll']['options'] as $option){
			$percentage = ($option['votes_count'] / $votes ) * 100;
			$output .= "<div class='polloption fixed' title='".$option['votes_count']." votes'><div class='voteBar' style='font-weight:bold; max-width:".$percentage."%;padding:1px; height:10px;'> </div>".$option['title']."</div>";
		}
	} else {
		foreach ($elem['poll']['options'] as $option){
			$output .= "<div class='polloption'>".$option['title']."</div>";
		}
		$output .= ($logedin ? "<input type='submit' class='vote' id='".$elem['poll']['id']."' value='Send Vote' style='padding:2px;' onClick='return false;'>" : "");
	}
	return $output;
}


/* this function is used to generate the html code of a reply */
function render_reply($item) {
	global $user_settings;
	global $logedin;
	global $srv;

	$reply['mode'] = "";
	if (isset($item['type']) && $item['type'] == 'ancestor') {
		$reply['mode'] = "ancestor";
	}

	$reply['id'] = $item['id'];
	$reply['uid'] = $item['account']['id'];
	$reply['name'] = emojify($item['account']['display_name'], $item['account']['emojis'], 20);
	$reply['acct'] = $item['account']['acct'];
	$reply['handle'] = "@".explode("@",$item['account']['acct'])[0];
	$reply['avatar'] = $item['account']['avatar'];

	$reply['menu'] = "<ul>";
	if ($logedin) {
		$reply['menu'] .= ($item['account']['id'] == $user_settings['uid'] ? "<li><a href='?action=delete&thread=" . $item['id'] . "' onClick='return false;' class='delete fontello' id=':id:'>&#xe80e; Delete Post</a></li>" : "");
		$reply['menu'] .= "<li><a href='?action=compose&quote=" . $item['id'] . "' onClick='return false;' class='quote fontello' id='" . $item['id'] . "' style='background-color:transparent;'>&#xf10e; Quote Post</a></li>";
		$reply['menu'] .= ($item['account']['id'] != $user_settings['uid'] ? "<li><a href='?action=mute&user=" . $item['account']['id'] . "' onClick='return false;' class='mute fontello' id='" . $item['account']['id'] . "' style='background-color:transparent;'>&#xe81b; Mute User</a></li>" : "");
		$reply['menu'] .= ($item['account']['id'] != $user_settings['uid'] ? "<li><a href='?action=mute&thread=" . $item['account']['id'] . "' onClick='return false;' class='muteconv fontello' id='" . $item['id'] . "' style='background-color:transparent;'>&#xf1f7; Drop Thread</a></li>" : "");
		$reply['menu'] .= (isset($user_settings['pleroma']) ? "<li><a href='?action=hide&thread=" . $item['pleroma']['conversation_id'] . "' onClick='return false;' class='hide fontello' id='" . $item['pleroma']['conversation_id'] . "' style='background-color:transparent;'>&#xf1f8; Hide Thread</a></li>" : "");
		$reply['menu'] .= (isset($user_settings['pleroma']) ? "<li><a href='?action=bookmark&thread=" . $item['account']['id'] . "' onClick='return false;' class='" . ($item['bookmarked'] == true ? "un" : "") . "bookmark fontello' id='" . $item['id'] . "' style='background-color:transparent;'>&#xe81e; " . ($item['bookmarked'] == true ? "Unb" : "B") . "ookmark</a></li>" : "");
		$reply['menu'] .= ($item['account']['id'] != $user_settings['uid'] ? "<li><a href='?action=nsfw&user=" . $item['account']['id'] . "' onClick='return false;' class='nsfw fontello' id='" . $item['account']['id'] . "' style='background-color:transparent;'>&#xe829; User is NSFW</a></li>" : "");
	}
	$reply['menu'] .= "<li><a target='_blank' href='" . $item['url'] . "' class='original link fontello'  style='background-color:transparent;'>&#xf14c; Original Note</a></li>";
	$reply['menu'] .= "</ul>";

	$json['id'] = $item['id'];
	$json['scope'] = $item['visibility'];
	if ($logedin) {
		$json['mentions'] = "";
		$array = $item["mentions"];
		$json['mentions'] = ($user_settings['acct'] == $item["account"]['acct'] ? "" : "@" . $item["account"]['acct']) . " ";
		if (!empty($array)) {
			foreach ($array as $mnt) {
				if ($mnt['acct'] != $user_settings['acct']) {
					$json['mentions'] .= "@" . $mnt['acct'] . " ";
				}
			}
		}
	}
	$reply['json'] = json_encode($json);

	$reply['replyto'] = ($item['in_reply_to_id'] ? " <a class='fontello link preview ldr' target='_blank' id='" . $item['in_reply_to_id'] . "' href='?thread=" . $item['in_reply_to_id'] . "'>&#xf112;</a> " : "");
	$reply['text'] = processText($item);

	$reply['date'] = "<a class='ldr postAge' id='".strtotime($item['created_at'])."' style='text-decoration:none;' target='_blank' href='?thread=" . $item['id'] . "'>" . time_elapsed_string($item['created_at']) . "</a>";
	$reply['visibility'] = $item['visibility'];

	$reply['media'] = "";
	if (!empty($item['media_attachments'])) {
		$reply['media'] = "<div style='width:170px; display:inline-block; float:left; margin:15px 0px 10px 0px;'>";
		$images = count($item['media_attachments']);
		$class = ($images > 1 ? "class='icon'" : "");
		foreach ($item['media_attachments'] as $file) {
			$ext = explode(".", $file['url']);
			$ext = end($ext);
			$ext = explode("?", $ext) [0];
			if (in_array($ext,array('webm','mp4','ogv'))) {
				$reply['media'] .= "<div style='text-align:center; width:100%;'><video preload='metadata' width='100%' controls ".($user_settings['videoloop'] == "on" ? "loop" : "").">
							<source src='" . $file['url'] . "' type='video/".($ext == "ogv" ? "ogg" : $ext)."'>
							</video></div>
						";
			}
			elseif (in_array($ext,array('mp3','ogg','oga','opus'))) {
				$reply['media'] .= "<div style='text-align:center; width:100%;'><audio controls>
												<source src='" . $file['url'] . "' type='audio/$ext'>
												Your browser does not support the audio tag.
											</audio> </div>";
			}
			else {
				if ($item['sensitive'] == true && $user_settings['explicit'] != 'off') {
					$reply['media'] .= "<div style='overflow:hidden; float:left; margin:2px;' $class><a target='_blank' href='" . $file['url'] . "' onClick='return false;' class='blur'><noscript><img src='" . $file['url'] . "' style='width:100%;'></noscript><img " . "data-src='" . $file['url'] . "'" . " class='' style='max-width:100%; max-height:100% vertical-align:middle;'></a><a target='_blank' href='" . $file['url'] . "' onClick='return false;' class='open-lightbox' style='display:none;'><img src='" . $file['url'] . "' class='' style='width:100%;'></a></div>";
				}
				else {
					$reply['media'] .= "<div style='margin:0px;' $class><a target='_blank' href='" . $file['url'] . "' onClick='return false;' class='open-lightbox'><img src='" . $file['url'] . "'" . " class='' style='max-width:100%; max-height:100%; vertical-align:middle;'><noscript><img src='" . $file['url'] . "' style='width:100%;'></noscript></a></div>";

				}
			}
		}
		$reply['media'] .= "</div>";
	}

	$reply['buttons'] = "
		" . ($logedin ? "<div class='felem'><a onClick='return false' class='replyform' href='?thread=" . $item['id'] . "' style='font-family:fontello'>&#xf112;</a></div>" : "") . "
					<div class='felem'><a onClick='return false' " . ($logedin ? "class='" . ($item['favourited'] == true ? "unfav" : "fav") . "' href='?action=fav&thread=" . $item['id'] . "'" : "") . " style='font-family:fontello'>&#xe802; <span>" . $item['favourites_count'] . "</span></a></div>
					<div class='felem'><a onClick='return false' " . ($logedin && ($item['visibility'] != "private" || $item['visibility'] != "direct") ? "class='" . ($item['reblogged'] == true ? "unreblog" : "reblog") . "' href='?action=reblog&thread=" . $item['id'] . "'" : "") . " style='font-family:fontello'>&#xe83a; <span>" . $item['reblogs_count'] . "</span></a></div>
		";

	$result = themes("get","templates/reply.txt");

	foreach ($reply as $key => $elem) {
		$result = str_replace(":$key:", $elem, $result);
	}

	return $result;
}

/* this is the same as above but is used on other places, like user bios and places where the 
shortcodes and the emoji url is defined in the same place */
function emojify($string, $emojis, $size = 40) {
	foreach ($emojis as $emoji) {
		$string = str_replace(":" . $emoji['shortcode'] . ":", "<img class='emoji' alt='" . $emoji['shortcode'] . "' title='" . $emoji['shortcode'] . "' src='" . $emoji['url'] . "' height=$size style='vertical-align: middle;'>", $string);
	}
	return $string;
}

/* This function displays the emoji list of an instance based on a search 
   string given on $val */
function emoji_list($val){
	$emojilist = api_get("/custom_emojis");
	$c = 0;
	$return = "";
	foreach ($emojilist as $emoji){
		if (starts_with($emoji['shortcode'],$val) && $c < 50){
			$return .= "<img style='margin:1px;' src='".$emoji['static_url']."' class='emoji' title='".$emoji['shortcode']."' height=40>";
			$c++;
		}
	}
	if ($c < 50){
		foreach ($emojilist as $emoji){
			if ((contains($emoji['shortcode'],$val) && !starts_with($emoji['shortcode'],$val)) && $c < 50){
				$return .= "<img style='margin:1px;' src='".$emoji['static_url']."' class='emoji' title='".$emoji['shortcode']."' height=40>";
				$c++;
			}
		}
	}
	
	return $return;
}

function contact_search($val){
	global $user_settings;
	$return = "";
	$list = api_get("/accounts/search?q=".$val);
	foreach ($list as $contact){
		$return .= "<div class='contact' title='@".$contact['acct']."' style='width:100%; clear:both; height:40px; display:inline-block;'>
			<div style='width:40px; height:40px; background-size:cover; background-image:url(".$contact['avatar']."); float:left;'></div>
			<div>
				<span style='font-weight:bold;'>".emojify($contact['display_name'], $contact['emojis'], 15)."</span><br>
				<span style='font-size:12px;'>".$contact['acct']."</span>
			</div>
		</div>";
	}
	return $return;
}

/* This function will fetch and render all the notifications since an $id
   or get a list of all past notification ($max notifications specified)
   */
function getnotif($id = false, $max = false) {
	global $srv;
	global $token;
	global $user_settings;
	$n = "";
	
	$exclude = "";
	$exclude .= (str_split($user_settings['notif'])[0] == 0 ? "&exclude_types[]=favourite" : "");
	$exclude .= (str_split($user_settings['notif'])[1] == 0 ? "&exclude_types[]=reblog" : "");
	$exclude .= (str_split($user_settings['notif'])[2] == 0 ? "&exclude_types[]=mention" : "");
	$exclude .= (str_split($user_settings['notif'])[3] == 0 ? "&exclude_types[]=follow" : "");
	
	$notif = api_get("notifications?" . ($id == false ? "limit=9&" : "") . ($id != false ? ($max == true ? "max_id=$id" : "since_id=$id") : "").$exclude);
	if (!empty($notif)) {
		foreach ($notif as $post) {
			if (!isset($post["type"])){
				break;
			}
			$user = "<a class='link ldr uname' style='font-size:12px;' href='?user=" . $post['account']['id'] . "'>" . (empty($post['account']['display_name']) ? $post['account']['acct'] : emojify($post['account']['display_name'], $post['account']['emojis'], 10)) . "</a>";
			$preview = "";
			$buttons = "";
			$media = "";

			if (!in_array($post["type"],array("mention","favourite","reblog","follow"))){
				continue;
			}

			switch ($post["type"]) {
				case "mention":
					if ($post['status']['in_reply_to_id'] == null) {
						$type = "<span class='fontello' style='color:#62C2CC; font-size:10px;'>&#xf10d;</span>";
						$string = "mentioned you in a post";
						$preview = "<a style='text-decoration:none;' class='ldr text' href='?thread=" . $post['status']['id'] . "' target='_blank'><span style='display:block; opacity:1; font-size:10px; line-height:12px;'>" . emojify(strip_tags(trim($post['status']['content']) , '<br><br \>') , $post['status']['emojis'], 15) . "</span></a>";
						$media = (!empty($post['status']['media_attachments']) ? "<a style='text-decoration:none;' class='ldr' href='?thread=" . $post['status']['id'] . "' target='_blank'><div class='notifpic' style='flex: 0 0 60px; background-size:cover; background-image:url(" . $post['status']['media_attachments'][0]['url'] . ");'></div></a>" : "");
					}
					else {
						$type = "<span class='fontello' style='color:#62C2CC; font-size:10px;'>&#xf112;</span>";
						$string = "replied to your post";

						foreach ($post['status']['mentions'] as $mention) {
							if(!contains($post['status']['content'],$mention['username'])) { 		
								$post['status']['content'] = "@" . $mention['username'] . " ".$post['status']['content']; 
							}
						}
						
						$preview = "<a style='text-decoration:none;' class='ldr text' href='?thread=" . $post['status']['id'] . "' target='_blank'><span style='display:block; opacity:1; font-size:10px; line-height:12px;'>" . emojify(strip_tags(trim($post['status']['content']) , '<br><br \>') , $post['status']['emojis'], 15) . "</span></a>";
						$media = (!empty($post['status']['media_attachments']) ? "<div class='notifpic' style='flex: 0 0 60px; background-size:cover; background-image:url(" . $post['status']['media_attachments'][0]['url'] . ");'></div>" : "");
					}

					$array = $post['status']["mentions"];
					$mentions = ($user_settings['acct'] == $post['status']['account']['acct'] ? "" : "@" . $post['status']['account']['acct']) . " ";
					if (!empty($array)) {
						foreach ($array as $mnt) {
							if ($mnt['acct'] != $user_settings['acct']) {
								$mentions .= "@" . $mnt['acct'] . " ";
							}
						}
					}

					$buttons = "<div class='post_buttons' id='" . $post['status']['id'] . "' style='position:absolute; right:10px; bottom:10px; border-radius:60px; padding:5px;'>
					<div class='felem'><a onClick='return false' class='quickreply' id='" . $post['status']['id'] . "' data-mentions='" . $mentions . "' href='?thread=" . $post['status']['id'] . "' style='font-family:fontello'>&#xe824;</a></div>
					<div class='felem'><a onClick='return false' class='" . ($post['status']['favourited'] == true ? "unfav" : "fav") . "' href='?action=fav&thread=" . $post['status']['id'] . "'" . " style='font-family:fontello'>&#xe802;</a></div>
					<div class='felem'><a onClick='return false' " . ($post['status']['visibility'] == "public" || $post['status']['visibility'] == "unlisted" ? "class='" . ($post['status']['reblogged'] == true ? "unreblog" : "reblog") . "' href='?action=reblog&thread=" . $post['status']['id'] . "'" : "") . " style='font-family:fontello'>&#xe83a;</a></div></div>";
				break;

				case "favourite":
					$type = "<span class='fontello' style='color:#F17022; font-size:10px;'>&#xe802;</span>";
					$string = "favourited your post";
					$preview = "<a style='text-decoration:none;' class='ldr text' href='?thread=" . $post['status']['id'] . "' target='_blank'><span style='display:block; opacity:1; font-size:10px; line-height:12px;'>" . (!empty($post['status']['content']) ? emojify(strip_tags(trim($post['status']['content']) , '<br><br \>') , $post['status']['emojis'], 15) : "Favourited your image") . "</span></a>";
					$media = (!empty($post['status']['media_attachments']) ? "<div class='notifpic'  style='flex: 0 0 60px; background-size:cover; background-image:url(" . $post['status']['media_attachments'][0]['url'] . ");'></div>" : "");
				break;

				case "reblog":
					$type = "<span class='fontello' style='color:#D1DC29; font-size:10px;'>&#xe826;</span>";
					$string = "reblogged your post";
					$preview = "<a style='text-decoration:none;' class='ldr text' href='?thread=" . $post['status']['id'] . "' target='_blank'><span style='display:block; opacity:1; font-size:10px; line-height:12px;'>" . (!empty($post['status']['content']) ? emojify(strip_tags(trim($post['status']['content']) , '<br><br \>') , $post['status']['emojis'], 15) : "Reblogged your image") . "</span></a>";
					@$media = (!is_null($post['status']['media_attachments']) ? "<div class='notifpic'  style='flex: 0 0 60px; background-size:cover; background-image:url(" . $post['status']['media_attachments'][0]['url'] . ");'></div>" : "");
				break;

				case "follow":
					list($info, $rel) = user_info($post["account"]["id"]);
					$type = "<span class='fontello' style='color:#FDB813; font-size;10px;'>&#xf234;</span>";
					$preview = "started following you";
					if ($rel[0]['following']) {
						$label = "&#xe80c; Following";
						$class = "unfollow";
					}
					else {
						if ($info['locked']) {
							if ($rel[0]['requested']) {
								$label = "&#xe806; 	Follow Requested";
								$class = "unfollow";
							}
							else {
								$label = "&#xe806; Request Follow";
								$class = "follow";
							}
						}
						else {
							$label = "&#xf234; Follow";
							$class = "follow";
						}
					}
					$buttons .= "<div class='post_buttons' style='position:absolute; right:10px; bottom:10px; border-radius:60px; padding:5px;'><span id='" . $info['id'] . "' class='profileButton $class' style='background-color:white; font-family:sans,fontello ;font-size:13px;'>$label</span></div>";
				break;
			}

			$n .= "
			<div class='notif " . ($id == false ? "" : "new") . "' id='" . $post['id'] . "'>
				<div class='notifContents'>
					<div style='flex: 0 0 60px; background-size:cover; background-image:url(" . $post['account']['avatar'] . "); border-radius:5px;'></div>
					<div style='flex: 1; padding-left:5px; padding-right:5px; word-break: break-word; overflow:hidden;'>
					<span>$type <span style='font-size:12px; font-weight:bold;'>$user</span></span>
					" . trim($preview) . "
					$buttons
					</div>
					$media
				</div>
			</div>
			";

		}
		return $n;
	}
}

/* function to parse opengraph from a html source */
/* taken from https://ajaxhispano.com/ask/como-obtener-el-protocolo-open-graph-de-una-pagina-web-por-php-109483/ */
function getOgTags($html)
{
    $pattern='/<\s*meta\s+property="og:([^"]+)"\s+content="([^"]*)/i';
    if(preg_match_all($pattern, $html, $out))
        return array_combine($out[1], $out[2]);
    return array();
}


/* this function takes in a whole post entity and spits out the HTMLfied text of the post
   with the urls and @handles linkified and all the emojis replaced */
function processText($elem) {
	global $user_settings;
	global $logedin;
	require_once "vendor/simple_html_dom.php";
	$content = trim(html_entity_decode($elem['content'],ENT_QUOTES));
	$content = preg_replace("/<(?=[^>]*(?:<|$))/","&lt;",$content);
	
	if (!empty($content)) {
		$html = str_get_html($content);
		foreach ($html->find('a') as $lnk) {
			
			//remove text links to media attachments
			foreach ($elem['media_attachments'] as $f) {
				if (is_numeric(strpos($f['description'],explode("…",$lnk->innertext)[0]))) {
					$content = str_replace($lnk->outertext . "<br/>", null, $content);
					$content = str_replace("<br/>" . $lnk->outertext, null, $content);
					$content = str_replace($lnk->outertext, null, $content);
				}
			}
			
			//modify links for hashtags and external urls
			if (is_numeric(strpos($lnk->href, $user_settings['instance'])) || in_array($lnk->class, array(
				"u-url mention",
				"hashtag"
			)) || $lnk->rel == "tag") {
				$content = str_replace($lnk->outertext, $lnk->innertext, $content);
			}
			else {
				$prv = $lnk->outertext;
				$lnk->target = '_blank';
				$lnk->class = 'link external';
				$content = str_replace($prv, $lnk->outertext, $content);
			}
		}
	}
	
	$result = strip_tags($content, '<br><p><strong><a><em><strike>');
	$result = str_replace('<br />', ' <br>', $result);
	
	foreach ($elem['mentions'] as $mention) {
		if(contains($result,"@".$mention['username'])) { 		
			$result = str_replace("@" . $mention['username'], "<span class='user' id='" . $mention['id'] . "'><a href='?user=" . $mention['id'] . "' class='link ldr' onClick='return false;'>@" . $mention['username'] . "</a></span>", $result);
		} else {
			$result = "<span class='user' id='" . $mention['id'] . "'><a href='?user=" . $mention['id'] . "' class='link ldr' onClick='return false;'>@" . $mention['username'] . "</a></span> ".$result; 
		}
	}

	$result = emojify($result, $elem['emojis']);
	
	/* 	We convert hashtags to clickable links. The regex detects only strings that are not part of an url.
	If the user is not logged in, it just shows the hashtag in bold.
	Regex expression got it from here https://stackoverflow.com/questions/39414007/php-find-all-hashtags-but-no-in-link 
	*/
	if ($logedin){
		$result = preg_replace("!(?:f|ht)tps?://[-a-zA-Zа-яА-Я()0-9@:%_+.~#?&;/=]+(*SKIP)(*F)|#(\w+)!", "<a class='ldr' href=\"./?tag=$1\"><b>#$1</b></a>", $result);
	} else {
		$result = preg_replace("!(?:f|ht)tps?://[-a-zA-Zа-яА-Я()0-9@:%_+.~#?&;/=]+(*SKIP)(*F)|#(\w+)!", "<b>#$1</b>", $result);
	}

	return $result;
}

// OTHER FUNCTIONS

/* the purpose of this function is to encode the auth token
   it is not used for now */
function msc($string, $action = 'e') {
	// you may change these values to your own
	$secret_key = 'yAmfVhZwm0749FSY24dC';
	$secret_iv = 'm37uvAeKjYLKdI1lPkcJ';

	$output = false;
	$encrypt_method = "AES-256-CBC";
	$key = hash('sha256', $secret_key);
	$iv = substr(hash('sha256', $secret_iv) , 0, 16);

	if ($action == 'e') {
		$output = base64_encode(openssl_encrypt($string, $encrypt_method, $key, 0, $iv));
	}
	else if ($action == 'd') {
		$output = openssl_decrypt(base64_decode($string) , $encrypt_method, $key, 0, $iv);
	}

	return $output;
}

/* this function extracts the urls from a text and return them in an array */
function get_urls($input) {
	$pattern = '$(https?://[a-z0-9_./?=&-~]+)(?![^<>]*>)$i'; 
	if (preg_match_all($pattern, $input, $matches)) {
		list($dummy, $links) = ($matches);
		return $links;
	}
	return false;
}

/* general function to check if one strings starts with a given search */ 	
function starts_with($string,$search){
	if (substr(strtolower($string),0,strlen($search)) == strtolower($search)){
		return true;
	}
	return false;
}

/* general function to check if one strings contains with a given search */ 
function contains($string,$search){
	if (is_numeric(strpos(strtolower($string),strtolower($search)))){
		return true;
	}
	return false;
}

/* same as avobe but check against all elements of an array */
function contains_any($where, $array)
{
	$n = 1;
    foreach ($array as $elem)
    {
        if (is_numeric(strpos($where, $elem)))
        {
            return $n;
        }
		$n++;
    }
    return false;
}

/* this function just reduces an image to a 1x1 pixel image to get the overall color.
   */
function averageColor($url) {
	@$image = imagecreatefromstring(file_get_contents($url));

	if (!$image) {
		$mainColor = "CCCCCC";
	}
	else {
		$thumb = imagecreatetruecolor(1, 1);
		imagecopyresampled($thumb, $image, 0, 0, 0, 0, 1, 1, imagesx($image) , imagesy($image));
		$mainColor = strtoupper(dechex(imagecolorat($thumb, 0, 0)));
	}

	return $mainColor;
}

/* function used in the process of uploading a file */
function get_mime($filename) {
	$result = new finfo();

	if (is_resource($result) === true) {
		return $result->file($filename, FILEINFO_MIME_TYPE);
	}

	return false;
}

function sanitize($text){
	return preg_replace("/[^a-zA-Z0-9.]+/", "", $text);
}


function themes($mode,$name = false){
	global $user_settings;
	switch ($mode){
		case "list":
			$themes = scandir("themes/");
			$themelist = array();
			foreach ($themes as $elem){
				if ($elem != ".." && $elem != "." && $elem != "custom" && is_dir("themes/".$elem)){
					$themelist[] = $elem;
				}
			}
			return $themelist;
			
		case "file":
			$theme = sanitize($user_settings['theme']);
			if (file_exists("themes/$theme/$name")){
				return "themes/$theme/$name";
			} else {
				return "$name";
			}
			
		case "get":
			$theme = sanitize($user_settings['theme']);
			if (file_exists("themes/$theme/$name")){
				return file_get_contents("themes/$theme/$name");
			} else {
				return file_get_contents("$name");
			}
	}
}

function time_elapsed_string($datetime, $full = false) {
    $now = new DateTime;
    $ago = new DateTime($datetime);
    $diff = $now->diff($ago);

    $diff->w = floor($diff->d / 7);
    $diff->d -= $diff->w * 7;

    $string = array(
        'y' => 'year',
        'm' => 'month',
        'w' => 'week',
        'd' => 'day',
        'h' => 'hour',
        'i' => 'minute',
        's' => 'second',
    );
    foreach ($string as $k => &$v) {
        if ($diff->$k) {
            $v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
        } else {
            unset($string[$k]);
        }
    }

    if (!$full) $string = array_slice($string, 0, 1);
    return $string ? implode(', ', $string) . ' ago' : 'just now';
}

function getHeaders($respHeaders) {
    $headers = array();

    $headerText = substr($respHeaders, 0, strpos($respHeaders, "\r\n\r\n"));

    foreach (explode("\r\n", $headerText) as $i => $line) {
        if ($i === 0) {
            $headers['http_code'] = $line;
        } else {
            list ($key, $value) = explode(': ', $line);

            $headers[$key] = $value;
        }
    }

    return $headerText;
}
